package io.github.jesse205.transferstation.lite.managers;

// import static io.github.jesse205.transferstation.lite.helpers.ClipDataHelper.APP_LABEL;

import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import androidx.core.content.FileProvider;

import com.jesse205.util.FileUtil;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Objects;
import java.util.UUID;

import io.github.jesse205.transferstation.lite.BuildConfig;
import io.github.jesse205.transferstation.lite.R;
import io.github.jesse205.transferstation.lite.activities.DialogActivity;
import io.github.jesse205.transferstation.lite.helpers.FilesHelper;
import io.github.jesse205.transferstation.lite.utils.ExceptionUtil;


public class DragDropManager {
    public static final String NAME_DRAG_DIR = "drag_files";
    public static final String PROP_CHANGE_ITEM = "prop_change_item";
    public static final String PROP_ADD_ITEM = "prop_add_item";
    public static final String PROP_REMOVE_ITEM = "prop_remove_item";

    public static final String PROP_CLEAR_ALL_ITEMS = "prop_clear_all_item";
    private static final String TAG = "DragDropManager";
    private static final String APP_LABEL = "edde_dadts_lite";
    private static DragDropManager instance;
    private final ArrayList<DataHolder> itemArrayList;
    private final PropertyChangeSupport changeSupport;
    private final File selfExternalMediaDir;

    private DragDropManager(@NonNull File externalMediaDir) {
        this.selfExternalMediaDir = new File(externalMediaDir, NAME_DRAG_DIR);
        itemArrayList = new ArrayList<>();
        changeSupport = new PropertyChangeSupport(this);
    }

    public static DragDropManager getInstance(Context context) {
        if (instance == null) {
            // 没有外部存储就自动切换为数据分区
            File dir = context.getExternalCacheDir();
            if (dir == null) {
                dir = context.getCacheDir();
            }
            instance = new DragDropManager(dir);
        }

        return instance;
    }

    public ArrayList<DataHolder> getItemList() {
        return itemArrayList;
    }

    public void newItem(@NonNull Context context, @NonNull ClipData clipData) {
        DataHolder loadingHolder = new DataHolder();
        addItem(loadingHolder);
        new Thread(() -> {
            DataHolder holder = null;
            try {
                File storeDir = new File(selfExternalMediaDir,
                        String.valueOf(System.currentTimeMillis()) + UUID.randomUUID());
                int itemCount = clipData.getItemCount();
                DataItemHolder[] itemHolders = new DataItemHolder[itemCount];

                ClipDescription description = clipData.getDescription();
                // 复制 mimeTypes
                int mimeTypeCount = description.getMimeTypeCount();
                String[] mimeTypes = new String[mimeTypeCount];

                for (int i = 0; i < mimeTypeCount; i++) {
                    mimeTypes[i] = description.getMimeType(i);
                }

                ClipDescription newDescription = new ClipDescription(APP_LABEL, mimeTypes);
                if (description.getExtras() != null)
                    newDescription.setExtras(description.getExtras());

                // 必须先添加第一个项目，才能创建ClipData
                ClipData.Item item = clipData.getItemAt(0);
                DataItemHolder itemHolder = DragDropManager.this.copyClipDataItem(context, storeDir, 0, item, itemHolders);
                ClipData newClipData = new ClipData(newDescription, itemHolder.item);
                for (int i = 1; i < itemCount; i++) {
                    item = clipData.getItemAt(i);
                    itemHolder = DragDropManager.this.copyClipDataItem(context, storeDir, i, item, itemHolders);
                    newClipData.addItem(itemHolder.item);
                }
                holder = new DataHolder(newClipData, itemHolders);


            } catch (Exception e) {
                e.printStackTrace();
                Intent intent = new Intent(context, DialogActivity.class);
                intent.putExtra("title", R.string.error);
                intent.putExtra("message", ExceptionUtil.buildErrorMessage(e));
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
                context.startActivity(intent);
            } finally {
                int index = DragDropManager.this.getItemIndex(loadingHolder);
                if (index != -1) {
                    if (holder != null) {
                        changeItem(index, holder);
                    } else
                        DragDropManager.this.removeItem(loadingHolder);
                } else
                    Log.w(TAG, "newItem: Cannot found index " + index + ", item will not be added.", new RuntimeException());
            }
        }).start();
    }



    private DataItemHolder copyClipDataItem(@NonNull Context context, @NonNull File storeDir,
                                            int index, @NonNull ClipData.Item item,
                                            @NonNull DataItemHolder[] itemHolders) {
        Uri uri = item.getUri();
        String title;
        File file;
        if (uri != null) {
            title = FilesHelper.getName(context, uri);
            String fileName = title;
            if (fileName == null) {
                title = String.valueOf(UUID.randomUUID());
                fileName = String.valueOf(UUID.randomUUID());
            }
            file = FileUtil.getNonDuplicateFile(new File(storeDir, fileName));
            // region 创建文件夹与创建文件
            try {
                // noinspection ResultOfMethodCallIgnored
                Objects.requireNonNull(file.getParentFile()).mkdirs();
                // noinspection ResultOfMethodCallIgnored
                file.createNewFile();

                try (InputStream inputStream = context.getContentResolver().openInputStream(uri);
                     FileOutputStream outputStream = new FileOutputStream(file)) {
                    if (inputStream != null) {
                        FileUtil.copyFile(inputStream, outputStream);
                        uri = FileProvider.getUriForFile(context,
                                BuildConfig.APPLICATION_ID + ".fileprovider", file);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
            // endregion
        } else {
            title = item.getText().toString();
            file = null;
        }
        ClipData.Item newItem = new ClipData.Item(item.getText(), item.getHtmlText(),
                item.getIntent(), uri);
        DataItemHolder itemHolder = new DataItemHolder(newItem, title, file);
        itemHolders[index] = itemHolder;
        return itemHolder;
    }

    public void destroyItem(int index) {
        if (index >= itemArrayList.size())
            return;
        DataHolder holder = itemArrayList.get(index);

        if (holder.itemHolders != null) {
            for (DataItemHolder itemHolder : holder.itemHolders) {
                File file = itemHolder.file;
                if (file != null && !file.delete()) {
                    file.deleteOnExit();
                }
            }
        }

        itemArrayList.remove(index);
    }

    public void destroyItem(DataHolder holder) {
        if (holder.itemHolders != null) {
            for (DataItemHolder itemHolder : holder.itemHolders) {
                File file = itemHolder.file;
                if (file != null && !file.delete()) {
                    file.deleteOnExit();
                }
            }
        }
        itemArrayList.remove(holder);
    }

    public void destroyAllItems() {
        for (DataHolder holder : itemArrayList) {
            if (holder.itemHolders != null) {
                for (DataItemHolder itemHolder : holder.itemHolders) {
                    File file = itemHolder.file;
                    if (file != null && !file.delete()) {
                        file.deleteOnExit();
                    }
                }
            }
        }
        clearAllItem();
    }


    /**
     * 将一个旧项目替换为新项目，并通知 changeSupport。如果oldItem不在列表内，将抛出一个警告
     *
     * @param oldItem 旧项目
     * @param item    项目
     */
    private void changeItem(DataHolder oldItem, DataHolder item) {
        int index = -1;
        synchronized (this) {
            index = getItemIndex(oldItem);
            if (index != -1) {
                changeItem(index, item);
                itemArrayList.set(index, item);

            } else
                Log.w(TAG, "newItem: Cannot found index " + index + ", item will not be added.", new RuntimeException());
        }
        if (index != -1) {
            changeSupport.fireIndexedPropertyChange(PROP_CHANGE_ITEM, index, oldItem, item);
        }
    }

    /**
     * 改变指定索引的项目，并通知 changeSupport
     *
     * @param index 索引
     * @param item  项目
     */
    private void changeItem(int index, DataHolder item) {
        DataHolder oldHolder = null;
        synchronized (this) {
            oldHolder = itemArrayList.get(index);
            itemArrayList.set(index, item);
        }
        changeSupport.fireIndexedPropertyChange(PROP_CHANGE_ITEM, index, oldHolder, item);

    }

    /**
     * 添加一个项目到末尾，并返回索引位置，并通知 changeSupport。如果添加失败，则返回 -1。
     *
     * @param item 数据持有者
     * @return 位置
     */
    private int addItem(@NonNull DataHolder item) {
        int position = -1;
        synchronized (this) {
            if (itemArrayList.add(item)) {
                position = itemArrayList.size() - 1;


            }
        }
        if (position != -1) {
            changeSupport.fireIndexedPropertyChange(PROP_ADD_ITEM,
                    position, null, item);
        }
        return position;
    }

    private void removeItem(DataHolder item) {
        boolean flag = false;
        synchronized (this) {
            flag = itemArrayList.remove(item);
        }
        if (flag) {
            changeSupport.fireIndexedPropertyChange(PROP_REMOVE_ITEM,
                    itemArrayList.size() - 1, null, item);
        }
    }

    private void clearAllItem() {
        int size;
        synchronized (this) {
            size = itemArrayList.size();
            itemArrayList.clear();
        }
        changeSupport.firePropertyChange(PROP_CLEAR_ALL_ITEMS,
                size, 0);
    }


    public int getItemCount() {
        return itemArrayList.size();
    }

    @NonNull
    public DataHolder getItem(int index) {
        return itemArrayList.get(index);
    }

    public int getItemIndex(DataHolder holder) {
        return itemArrayList.indexOf(holder);
    }

    public void destory() {
        instance = null;
        FileUtil.deleteDirs(selfExternalMediaDir);
    }

    public boolean hasData() {
        return getItemCount() > 0;
    }

    public void addListener(PropertyChangeListener listener) {
        changeSupport.addPropertyChangeListener(listener);
    }

    public void removeListener(PropertyChangeListener listener) {
        changeSupport.removePropertyChangeListener(listener);
    }

    public enum DataType {
        FILE, TEXT, LOADING, UNKNOWN
    }

    public static class DataHolder {
        @Nullable
        public final ClipData clipData;
        @NonNull
        public final DataType type;
        @Nullable
        public final String title;
        public final DataItemHolder[] itemHolders;


        public DataHolder(@NonNull ClipData clipData, DataItemHolder[] itemHolders) {
            this.clipData = clipData;
            this.itemHolders = itemHolders;
            DataItemHolder item = itemHolders[0];
            title = item.title;
            type = item.type;
        }

        public DataHolder() {
            clipData = null;
            title = null;
            type = DataType.LOADING;
            itemHolders = null;
        }

        @Nullable
        public ClipData getClipData() {
            return clipData;
        }

        @Nullable
        public String getTitle() {
            return title;
        }

        @NonNull
        public DataType getType() {
            return type;
        }

    }

    public static class DataItemHolder {

        public final ClipData.Item item;
        public final DataType type;
        public final File file;
        @Nullable
        public final String title;

        public DataItemHolder(ClipData.Item item, @Nullable String title, @Nullable File file) {
            this.item = item;
            this.file = file;
            this.title = title;
            if (item.getText() != null) {
                type = DataType.TEXT;
            } else if (item.getUri() != null) {
                type = DataType.FILE;
            } else {
                type = DataType.UNKNOWN;
            }
        }
    }
}
